﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Threading;

using MediaInfoLib;

namespace VideoAsset
{
    public class VideoAssets
    {
        private const string m_titleTxt = @"e:\bbb.txt";
        private const string m_titleError = @"e:\error.txt";
        private const string m_actorListFile = "e:\\actor.txt";
        private const string m_mediaInfoFile = "e:\\media.txt";
        public const string UNKNOW_ID = "__";

        private string m_root;

        public class VideoMediaInfo
        {
            public VideoMediaInfo(UInt32 x, UInt32 y) { resX = x; resY = y; }
            public UInt32 resX;
            public UInt32 resY;
        };

        public class VideoFileInfo : INotifyPropertyChanged
        {
            public string name { get; set; }
            public string time { get; set; }
            public UInt32 size { get; set; }
            public bool exist { get; set; }
            public VideoMediaInfo info;

            public string cover { get; set; }
            public string title { get; set; }
            public string id { get; set; }
            public string act { get; set; }

            public event PropertyChangedEventHandler PropertyChanged;
            public string coverPath
            {
                get { return cover; }
                set
                {
                    cover = value;
                    OnPropertyChanged("coverPath");
                }
            }
            public string actor
            {
                get { return act; }
                set
                {
                    act = value;
                    OnPropertyChanged("actor");
                }
            }
            public string resolution
            {
                get { return (info.resX == 0 || info.resY == 0) ? "" : string.Format("{0}x{1}", info.resX, info.resY); }
                set
                {
                    OnPropertyChanged("resolution");
                }
            }
            public string sizeString
            {
                get { return string.Format("{0:0,0}", size); }
                set { UInt32 i;  size = UInt32.TryParse(value, out i) ? size : 0; }
            }
            public string idString
            {
                get { return id.IndexOf(UNKNOW_ID) == 0 ? "" : id; }
            }
            // Create the OnPropertyChanged method to raise the event 
            protected void OnPropertyChanged(string name)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(name));
                }
            }

            public VideoFileInfo ShallowCopy()
            {
                return (VideoFileInfo)this.MemberwiseClone();
            }
        }

        public List<VideoFileInfo> m_fileList;
        public List<string> m_actorList;
        public List<string> m_folderList;
        public Dictionary<string, VideoFileInfo> m_dbFileDict;
        public Dictionary<string, VideoMediaInfo> m_dbMediaInfo;
        private MediaInfo m_mediaInfo;
        private Thread m_mediaInfoThread;
        public UInt32 m_writeProgess { get; set; }
        private UInt32 m_unknowIdx;

        public VideoAssets()
        {
            m_root = Properties.Settings.Default.root;
            m_fileList = new List<VideoFileInfo>();
            m_dbFileDict = new Dictionary<string, VideoFileInfo>();
            m_dbMediaInfo = new Dictionary<string,VideoMediaInfo>();            
            m_mediaInfo = new MediaInfo();
            m_unknowIdx = 0;
        }

        public void InitDB()
        {
            LoadActorList();
            LoadDatabase();
            LoadMediaInfo();
        }

        public void RetrieveMediaInfo(VideoFileInfo vfi)
        {
            m_mediaInfo.Open(vfi.name);
            string width = m_mediaInfo.Get(StreamKind.Video, 0, "Width");
            string height = m_mediaInfo.Get(StreamKind.Video, 0, "Height");
            vfi.info.resX = UInt32.Parse(width);
            vfi.info.resY = UInt32.Parse(height);            
            m_mediaInfo.Close();
        }

        public void WriteMediaInfo()
        {            
            string output = "";
            UInt32 count = 0;
            foreach (VideoFileInfo vfi in m_fileList)
            {
                if (vfi.info.resX == 0 || vfi.info.resY == 0)
                {
                    RetrieveMediaInfo(vfi);
                    output += string.Format("{0}?{1}?{2}\r\n", vfi.name, vfi.info.resX, vfi.info.resY);
                }
                m_dbMediaInfo[vfi.name] = vfi.info;

                m_writeProgess = (UInt32)((float)(count++) / (float)m_fileList.Count * 100);
            }
            
            if(output != "")
            {
                bool append = (new FileInfo(m_mediaInfoFile)).Exists ? true : false;
                StreamWriter sw = new StreamWriter(m_mediaInfoFile, append, System.Text.Encoding.UTF8);
                sw.Write(output);
                sw.Close();
            }
            m_writeProgess = 0;
        }

        public void LoadMediaInfo()
        {
            m_dbMediaInfo.Clear();
            if ((new FileInfo(m_mediaInfoFile)).Exists)
            {
                StreamReader sr = new StreamReader(m_mediaInfoFile, System.Text.Encoding.UTF8);
                UInt32 resX;
                UInt32 resY;
                while (sr.Peek() >= 0)
                {
                    string line = sr.ReadLine();
                    var parts = line.Split('?');
                    string file = parts[0];
                    resX = Convert.ToUInt32(parts[1]);
                    resY = Convert.ToUInt32(parts[2]);
                    m_dbMediaInfo[file] = new VideoMediaInfo(resX, resY);
                }

                sr.Close();
            }
        }

        public VideoMediaInfo GetMediaInfo(VideoFileInfo vfi)
        {
            VideoMediaInfo vmi;
            if (m_dbMediaInfo.TryGetValue(vfi.name, out vmi))
            {
                return vmi;
            }
            else
            {
                return new VideoMediaInfo(0, 0);
            }
            
        }

        public void UpdateFileList()
        {
            string error = "";
            string duplication = "";
            DirectoryInfo di = new DirectoryInfo(m_root);
            var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories).Where(s => s.FullName.EndsWith(".mp4") || s.FullName.EndsWith(".avi") || s.FullName.EndsWith(".wmv"));
            m_fileList.Clear();            
            foreach (FileInfo f in files)
            {
                string fileName = f.Name;
                string id = GetIDFromTitle(fileName);
                VideoFileInfo vfi = (id != "") ? GetFileInfoFromDB(id) : null;
                bool newFile = false;

                if (vfi != null && vfi.name != "")
                {
                    duplication += f.FullName + " --> " + vfi.name + "\r\n";
                    VideoFileInfo newVfi = new VideoFileInfo();
                    newVfi = (VideoFileInfo)vfi.ShallowCopy();
                    vfi = newVfi;
                }

                if (vfi == null) 
                {
                    vfi = new VideoFileInfo();
                    vfi.id = id;
                    vfi.title = fileName;
                    vfi.act = GetActor(fileName);  //try find the actor name from file name
                    vfi.cover = "images\\refresh.jpg";

                    newFile = true;                    
                }

                if (id == "")
                {                    
                    vfi.id = UNKNOW_ID + CreateMD5Hash(f.Name);
                    error += f.FullName + "\r\n";
                }

                vfi.name = f.FullName;
                vfi.time = f.LastWriteTime.ToString();
                vfi.size = (UInt32)(f.Length / (1024));
                vfi.exist = true;                
                vfi.info = GetMediaInfo(vfi);

                if (newFile)
                {
                    VideoFileInfo tmpVfi;
                    if (m_dbFileDict.TryGetValue(vfi.id, out tmpVfi))
                    {
                        duplication += f.FullName + " --> " + tmpVfi.name + "\r\n";
                        vfi.id = string.Format("__{0:D3}", m_unknowIdx++);
                    }                    
                    m_dbFileDict.Add(vfi.id, vfi);
                }   
                m_fileList.Add(vfi);
            }

            m_mediaInfoThread = new Thread(new ThreadStart(WriteMediaInfo));
            m_mediaInfoThread.Start();


            if (error != "")
            {
                StreamWriter sw = new StreamWriter(m_titleError, true, System.Text.Encoding.UTF8);
                sw.WriteLine("--- ID not found in DB ---");
                sw.Write(error);
                sw.WriteLine("--- ID duplication ---");
                sw.Write(duplication);
                sw.Close();
            }
        }

        public void LoadActorList()
        {
            List<string> actors = new List<string>();
            StreamReader sr = new StreamReader(m_actorListFile, System.Text.Encoding.UTF8);
            while (sr.Peek() >= 0)
            {
                actors.Add(sr.ReadLine());
            }
            sr.Close();

            //sort by string length
            IEnumerable<string> query = from items in actors.Distinct() orderby items.Length descending select items;
            m_actorList = query.ToList();
        }

        public VideoFileInfo GetFileInfoFromDB(string id)
        {
            VideoFileInfo vfi = null;
            if (id != "" && id != null && m_dbFileDict.TryGetValue(id, out vfi))
            {
                return vfi;
            }
            return null;
        }

        public string GetFileID(string title)
        {
            // remove "-", replace brackets to " ", append a space in the end
            string newLine = title.Replace("-", "").Replace("(", " ").Replace(")", " ").Replace("[", " ").Replace("]", " ") + " ";

            // search the id
            string patternID = @"\s\w{2,6}\d{1,5}\s";
            Regex videoIDRegex = new Regex(patternID, RegexOptions.IgnoreCase);
            Match m = videoIDRegex.Match(newLine.ToUpper());
            if (m.Success && m.Groups.Count > 0)
            {
                return m.Groups[0].Captures[0].ToString().Replace(" ", "");
            }
            return "";
        }

        public string GetIDFromTitle(string fileName)
        {            
            string patternID = @"\(\w{2,8}\d{1,5}\)";
            Regex videoIDRegex = new Regex(patternID, RegexOptions.IgnoreCase | RegexOptions.RightToLeft );
            Match m = videoIDRegex.Match(fileName.Replace("-",""));
            if (m.Success && m.Groups.Count > 0)
            {
                string s = m.Groups[0].Captures[0].ToString();
                return s.Substring(1, s.Length - 2).ToUpper();
            }

            return GetFileID(" " + Path.GetFileNameWithoutExtension(fileName).Replace("-", "").Replace(".", " "));
        }


        public string GetActor(string title)
        {
            foreach (string act in m_actorList)
            {
                if (title.IndexOf(act) != -1)
                {
                    return act;
                }
            }
            #region NO_IN_USE
            //                     string patternActor = @" \w+\b";
            //                     Regex videoActorRegex = new Regex(patternActor, RegexOptions.IgnoreCase);                    
            //                     foreach (Match ma in videoActorRegex.Matches(lineUTF8))
            //                     {
            //                         if (ma.Success && ma.Groups.Count > 0)
            //                         {
            //                             videoActor = ma.Value.Replace(" ", "").Replace("(", "");
            //                             if (videoActor.Length < 5 && videoActor.Length > 3)
            //                             {
            //                                 actors.Add(videoActor);
            //                             }
            //                         }
            //                     }
            #endregion
            return "";
        }

        public void LoadDatabase()
        {
            // (maker)title(FHD)(ID)
            StreamReader sr = new StreamReader(m_titleTxt, System.Text.Encoding.UTF8);

            string missingID = "";
            string missingActor = "";
            string duplicate = "";
            VideoMediaInfo emptyInfo = new VideoMediaInfo(0, 0);
            m_dbFileDict.Clear();
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                // search for ID, there are 4 patterns (AAA-001) (AAA001) AAA-001 AAA001
                if (line != "")
                {
                    // Convert utf-8 bytes to a string.
                    byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(line);
                    string lineUTF8 = System.Text.Encoding.UTF8.GetString(utf8Bytes);

                    string videoID = GetIDFromTitle(lineUTF8); ;
                    string videoActor = GetActor(lineUTF8);

                    if (videoID != "")
                    {
                        VideoFileInfo fi = new VideoFileInfo();
                        fi.name = "";
                        fi.time = "";
                        fi.size = 0;
                        fi.title = line;
                        fi.id = videoID;
                        fi.act = videoActor;
                        fi.info = emptyInfo;
                        fi.exist = false;
                        fi.cover = "images\\refresh.jpg";
                        if (!m_dbFileDict.ContainsKey(videoID))
                        {
                            m_dbFileDict.Add(videoID, fi);
                        }
                        else
                        {
                            duplicate += line + " --> " + m_dbFileDict[videoID].title + "\r\n";
                        }

                    }

                    missingID += videoID == "" ? line + "\r\n" : "";
                    missingActor += videoActor == "" ? line + "\r\n" : "";
                }
            }
            sr.Close();            

            StreamWriter sw = new StreamWriter(m_titleError, false, System.Text.Encoding.UTF8);
            sw.WriteLine("--- Duplication ---");
            sw.Write(duplicate);
            sw.WriteLine("--- Missing ID ---");
            sw.Write(missingID);
            sw.WriteLine("--- Missing Actor ---");
            sw.Write(missingActor);
            sw.Close();
        }

        public string CreateMD5Hash(string input)
        {
            // Use input string to calculate MD5 hash
            var md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hashBytes = md5.ComputeHash(inputBytes);

            // Convert the byte array to hexadecimal string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hashBytes.Length; i++)
            {
                sb.Append(hashBytes[i].ToString("X2"));
                // To force the hex string to lower-case letters instead of
                // upper-case, use he following line instead:
                // sb.Append(hashBytes[i].ToString("x2")); 
            }
            return sb.ToString();
        }
    }
}